﻿using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using ICSharpCode.AvalonEdit.Rendering;
using JiveMessenger.AvalonEdit;
using JiveMessenger.Models;
using JiveMessenger.User;
using JmShared;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows;
using System.Threading;
using JiveMessenger.Utilities;
using JiveMessenger.CustomEventArgs;
using ICSharpCode.AvalonEdit.Document;
using System.Collections.Specialized;
using NLog;
using System.Threading.Tasks;

namespace JiveMessenger.ViewModels
{
    public enum ContactListState
    {
        Wide,
        Short,
        Hidden
    }
    public abstract class ViewModelChat : ViewModelTab
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private object _lockObject = new object();
        private IClient _currentClient;
        protected bool _lastParagraphHadLine;
        private DispatcherTimer LastMessageReceivedTimer { get; set; }
        private Timer ChatLogTimer { get; set; }
        public IContact LastUserWritten { get; set; }
        public DateTime LastMessageReceivedTime { get; set; }
        public ChatLog ChatLog { get; set; }
        public Dictionary<string, DateTime> LastReceivedMessageTyping { get; set; }


        private bool _isScrolledToEnd;
        public bool IsScrolledToEnd
        {
            get { return _isScrolledToEnd; }
            set
            {
                if (_isScrolledToEnd != value)
                {
                    Set(() => IsScrolledToEnd, ref _isScrolledToEnd, value);
                }
            }
        }
        private int _remainingMessages;
        public int RemainingMessages
        {
            get { return _remainingMessages; }
            set
            {
                if (_remainingMessages != value)
                {
                    Set(() => RemainingMessages, ref _remainingMessages, value);
                }
            }
        }



        private Dictionary<int, IContact> _clientLines;
        public Dictionary<int, IContact> ClientLines
        {
            get { return _clientLines; }
            set
            {
                if (_clientLines != value)
                {
                    Set(() => ClientLines, ref _clientLines, value);

                }
            }
        }

        private TextDocument _inputDocument;
        public TextDocument InputDocument
        {
            get { return _inputDocument; }
            set
            {
                if (_inputDocument != value)
                {
                    Set(() => InputDocument, ref _inputDocument, value);
                }
            }
        }


        private TextDocument _document;
        public TextDocument Document
        {
            get { return _document; }
            set
            {
                if (_document != value)
                {
                    Set(() => Document, ref _document, value);
                }
            }
        }


        private bool _isNudgeButtonVisible;
        public bool IsNudgeButtonVisible
        {
            get { return _isNudgeButtonVisible; }
            set
            {
                if (_isNudgeButtonVisible != value)
                {
                    Set(() => IsNudgeButtonVisible, ref _isNudgeButtonVisible, value);
                }
            }
        }

        private bool _isInputTextEnabled;
        public bool IsInputTextEnabled
        {
            get { return _isInputTextEnabled; }
            set
            {
                if (_isInputTextEnabled != value)
                {
                    Set(() => IsInputTextEnabled, ref _isInputTextEnabled, value);
                }
            }
        }
        private string _chatControlStatusText;
        public string ChatControlStatusText
        {
            get { return _chatControlStatusText; }
            set
            {
                if (_chatControlStatusText != value)
                {
                    Set(() => ChatControlStatusText, ref _chatControlStatusText, value);
                }
            }
        }
        private bool _isInputTextFocused;
        public bool IsInputTextFocused
        {
            get { return _isInputTextFocused; }
            set
            {
                if (_isInputTextFocused != value)
                {
                    Set(() => IsInputTextFocused, ref _isInputTextFocused, value);
                }
            }
        }

        private double _scrollOffset;
        public double ScrollOffset
        {
            get { return _scrollOffset; }
            set
            {
                if (_scrollOffset != value)
                {
                    Set(() => ScrollOffset, ref _scrollOffset, value);
                }
            }
        }
        private bool _isFocused;
        public bool IsFocused
        {
            get { return _isFocused; }
            set
            {
                if (_isFocused != value)
                {
                    Set(() => IsFocused, ref _isFocused, value);
                }
            }
        }

        private bool _isDownloadMessagePopupOpen;
        public bool IsDownloadMessagePopupOpen
        {
            get { return _isDownloadMessagePopupOpen; }
            set
            {
                if (_isDownloadMessagePopupOpen != value)
                {
                    Set(() => IsDownloadMessagePopupOpen, ref _isDownloadMessagePopupOpen, value);
                }
            }
        }


        private ObservableCollection<VisualLineElementGenerator> _elementGenerators;
        public ObservableCollection<VisualLineElementGenerator> MessageInputElementGenerators
        {
            get { return _elementGenerators; }
            set
            {
                if (_elementGenerators != value)
                {
                    Set(() => MessageInputElementGenerators, ref _elementGenerators, value);
                }
            }
        }


        private ObservableCollection<VisualLineElementGenerator> _conversationElementGenerators;
        public ObservableCollection<VisualLineElementGenerator> ConversationElementGenerators
        {
            get { return _conversationElementGenerators; }
            set
            {
                if (_conversationElementGenerators != value)
                {
                    Set(() => ConversationElementGenerators, ref _conversationElementGenerators, value);
                }
            }
        }

        private ObservableCollection<IVisualLineTransformer> _conversationLineTransfomers;
        public ObservableCollection<IVisualLineTransformer> ConversationLineTransformers
        {
            get { return _conversationLineTransfomers; }
            set
            {
                if (_conversationLineTransfomers != value)
                {
                    Set(() => ConversationLineTransformers, ref _conversationLineTransfomers, value);
                }
            }
        }

        //public ViewModelChatControlBase() { }

        public ViewModelChat(IClient currentClient, string id, string name)
            : base(id, name)
        {
            SetDefaults();
            this._currentClient = currentClient;
            if (IsInDesignMode)
                return;


            this.IsNudgeButtonVisible = _currentClient.UserSettings.EnableNudge.GetValueOrDefault();
            InitElementGenerators();
            StartNowWritingTimer();
            StartChatLogTimer();

        }

        private void SetDefaults()
        {
            this.IsFocused = true;
            this.ShowAsTab = true;
            this.IsInputTextFocused = true;
            this.ClientLines = new Dictionary<int, IContact>();
            this.LastReceivedMessageTyping = new Dictionary<string, DateTime>();
            this.IsInputTextEnabled = true;
            this.LastUserWritten = null;
            this.RemainingMessages = 0;
            this.LastMessageReceivedTime = DateTime.MinValue;
            this.Document = new TextDocument();
            this.InputDocument = new TextDocument();
            this._lastParagraphHadLine = false;
        }

        private void StartNowWritingTimer()
        {
            this.LastMessageReceivedTimer = new DispatcherTimer();
            this.LastMessageReceivedTimer.Tick += this.LastMessageReceivedTimer_Tick;
            this.LastMessageReceivedTimer.Interval = new TimeSpan(0, 0, 1);
            this.LastMessageReceivedTimer.Start();
        }

        private void StartChatLogTimer()
        {
            this.ChatLogTimer = new System.Threading.Timer(x => WriteLog(), null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
        }

        private void InitElementGenerators()
        {
            this.MessageInputElementGenerators = new ObservableCollection<VisualLineElementGenerator>();
            this.MessageInputElementGenerators.Add(new ImageElementGenerator(_currentClient, this, this.ID, ClientLines, true));
            this.MessageInputElementGenerators.Add(new CustomLinkElementGenerator(ProgramSettings.Instance.HyperLinkRegex));

            this.ConversationElementGenerators = new ObservableCollection<VisualLineElementGenerator>();
            this.ConversationElementGenerators.Add(new ImageElementGenerator(_currentClient, this, this.ID, ClientLines, false));
            this.ConversationElementGenerators.Add(new ImageZoomLinkElementGenerator(ProgramSettings.Instance.HyperLinkRegex));



            this.ConversationLineTransformers = new ObservableCollection<IVisualLineTransformer>();
            this.ConversationLineTransformers.Add(new LineColorizer(JiveContactModel.RedText, ClientLines));
            this.ConversationLineTransformers.Add(new LineColorizer(JiveContactModel.GreenText, ClientLines));
            this.ConversationLineTransformers.Add(new LineColorizer(JiveContactModel.GrayText, ClientLines));
        }

        /// <summary>
        /// Timer that updates txtBlockNowWriting
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LastMessageReceivedTimer_Tick(object sender, EventArgs e)
        {
            DispatcherTimer timer = (DispatcherTimer)sender;
            timer.IsEnabled = false;
            try
            {
                bool clear = true;
                if (this.LastUserWritten == null)
                    return;
                string isWritingText = "";
                bool someoneWriting = false;

                foreach (KeyValuePair<string, DateTime> kvp in this.LastReceivedMessageTyping)
                {
                    if ((DateTime.Now.Subtract(kvp.Value).TotalSeconds > 3))
                        continue;

                    someoneWriting = true;
                    Messenger.Default.Send(new NotificationMessageAction<IContact>(this, kvp.Key, ViewModelContactList.MessengerCommands.GetContactFromUserID.ToString(), contact =>
                        {
                            if (contact != null)
                                isWritingText += contact.PrettyName;
                        }));
                    //if (_currentClient.ContactClients.ContainsKey(kvp.Key))
                    //    isWritingText += _currentClient.ContactClients[kvp.Key].PrettyName + ",";

                }
                if (someoneWriting)
                    clear = false;

                if (!String.IsNullOrEmpty(isWritingText) && !clear)
                {
                    isWritingText = isWritingText.TrimEnd(',');
                    isWritingText += " is writing.";
                }
                if (String.IsNullOrEmpty(isWritingText))
                    isWritingText = String.Format("Last message received at {0} on {1}.", this.LastMessageReceivedTime.ToShortTimeString(), this.LastMessageReceivedTime.ToShortDateString());//

                this.ChatControlStatusText = isWritingText;
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error in last message received timer", err);
            }
            finally
            {
                timer.IsEnabled = true;
            }
        }
        //  public abstract void DisplayMessage(IContact sender, string message, DisplayMessageOptions options, DateTime timestamp);
        public virtual void DisplayMessage(DisplayMessage displayMessage)
        {

            DisplayMessageOptions displayOptions = displayMessage.MessageOptions;
            string message = displayMessage.Message;
            IContact sender = displayMessage.Sender;
            DateTime timestamp = displayMessage.TimeStamp;
            string chatTarget = displayMessage.ChatTarget;
            this.Document.BeginUpdate();
            bool haveExistingUser = false;
            foreach (var transformer in this.ConversationLineTransformers)
            {
                if (transformer.GetType() == typeof(LineColorizer))
                {
                    LineColorizer c = (LineColorizer)transformer;

                    if (c.UserInfo.Equals(sender))
                    {
                        haveExistingUser = true;
                        break;
                    }
                }
            }

            if (!haveExistingUser)
                ConversationLineTransformers.Add(new LineColorizer(sender, ClientLines));

            List<string> messages = new List<string>();


            if (message.Length > 400)
            {
                messages = SharedStaticMethods.SplitLongMessage(message, 400);
            }
            else
                messages.Add(message);

            bool showInfoTimeStamp = displayOptions.ShowInfoTimeStamp; ;
            bool greenText = displayOptions.GreenText;
            bool redText = displayOptions.RedText;
            bool grayText = displayOptions.GrayText;
            bool addLine = displayOptions.AddLine; ;
            bool addSpacer = displayOptions.AddSpacer;

            bool spacerAdded = false;
            foreach (string str in messages)
            {
                string messagePart = str;
                bool writeName = true;
                IContact lineUser;
                if (displayOptions.ShowSender)
                {
                    if (this.LastUserWritten != null && this.LastUserWritten.Equals(sender))//LastUserWritten.UserName == user.UserName)
                    {
                        if (DateTime.Now.Subtract(this.LastMessageReceivedTime).TotalSeconds < 10)
                        {
                            writeName = false;
                        }
                        this.LastMessageReceivedTime = timestamp;
                    }
                    else
                    {
                        this.LastUserWritten = sender;
                        this.LastMessageReceivedTime = timestamp;
                    }
                    if (writeName || !_currentClient.UserSettings.GroupMessages.GetValueOrDefault())
                    {
                        lineUser = JiveContactModel.GrayText;
                        string saysMsg = sender.PrettyName + " says ";
                        if (_currentClient.UserSettings.ShowTimestamp.GetValueOrDefault())
                        {
                            string timeStamp = "(" + timestamp.ToShortTimeString() + "): ";
                            saysMsg += timeStamp;
                        }

                        if (ClientLines.Count == 0)
                        {
                            this.Document.Insert(this.Document.Lines.Last().EndOffset, saysMsg);//.Text += saysMsg;
                            if (!ClientLines.ContainsKey(1))
                                ClientLines.Add(1, lineUser);
                        }
                        else
                        {
                            this.Document.Insert(this.Document.Lines.Last().EndOffset, Environment.NewLine + saysMsg);
                            ClientLines.Add(this.Document.LineCount, lineUser);
                        }
                    }
                }

                if (greenText)
                    lineUser = JiveContactModel.GreenText;
                else if (redText)
                    lineUser = JiveContactModel.RedText;
                else if (grayText)
                    lineUser = JiveContactModel.GrayText;
                else
                    lineUser = sender;

                if (_currentClient.UserSettings.EnableChatLog.GetValueOrDefault(false))
                {
                    this.ChatLog.AddLogElement(new LogElement(timestamp, sender.UserName, chatTarget, messagePart, lineUser.FontFamily, lineUser.FontStyle, lineUser.FontWeight, lineUser.FontColor.Color));
                }

                if (addSpacer && !spacerAdded)
                {
                    messagePart = "<spacer>" + messagePart;
                    spacerAdded = true;
                }

                int firstLine = this.Document.LineCount;
                if (addLine && !_lastParagraphHadLine)
                {

                    if (ClientLines.Count == 0)
                    {
                        this.Document.Insert(this.Document.Lines.Last().EndOffset, "<line>");
                        ClientLines.Add(this.Document.LineCount, lineUser);
                    }
                    else
                    {
                        this.Document.Insert(this.Document.Lines.Last().EndOffset, Environment.NewLine + "<line>");
                        ClientLines.Add(this.Document.LineCount, lineUser);
                    }
                }
                ClientLines.Add(this.Document.LineCount + 1, lineUser);
                this.Document.Insert(this.Document.Lines.Last().EndOffset, Environment.NewLine + messagePart);


                if (addLine)
                {
                    ClientLines.Add(this.Document.LineCount + 1, lineUser);
                    this.Document.Insert(this.Document.Lines.Last().EndOffset, Environment.NewLine + "<line>");
                    _lastParagraphHadLine = true;
                }
                else
                    _lastParagraphHadLine = false;

                int lastLine = this.Document.LineCount;
                for (int i = firstLine + 1; i <= lastLine; i++)
                {
                    if (!ClientLines.ContainsKey(i))
                        ClientLines.Add(i, lineUser);
                }
            }
            this.LastReceivedMessageTyping[sender.UserID] = DateTime.MinValue;


            if (this.Document.LineCount > ProgramSettings.Instance.MaxLinesPerDocument + 100)
            {
                int lastLineIndexToRemove = this.Document.LineCount - ProgramSettings.Instance.MaxLinesPerDocument;
                var lastLineToRemove = this.Document.Lines[lastLineIndexToRemove - 1];
                this.Document.Remove(0, lastLineToRemove.EndOffset + lastLineToRemove.DelimiterLength);

                for (int i = 0; i < lastLineIndexToRemove; i++)
                {
                    this.ClientLines.Remove(i + 1);
                }

                Dictionary<int, IContact> newClientLines = new Dictionary<int, IContact>();
                int n = 1;
                foreach (KeyValuePair<int, IContact> kvp in ClientLines)
                {
                    newClientLines.Add(n, kvp.Value);
                    n++;
                }
                this.ClientLines.Clear();
                this.ClientLines.AddRange(newClientLines);
            }

            this.Document.EndUpdate();

            if (!IsInDesignMode)
            {
                if (displayMessage.MessageOptions.BlinkTab && !this.IsCurrentActiveTab)
                    this.IsBlinking = true;
            }
            Messenger.Default.Send(new NotificationMessage(this, "ScrollDocument"));
        }

        public void WriteLog()
        {
            if (ChatLog != null)
                ChatLog.WriteLog();
        }
        public void ClearLog()
        {
            this.ChatLog.Clear();
        }
        public virtual void SetStatus(bool offline)
        {
            if (offline)
            {
                IsInputTextEnabled = false;
                ChatLog.IncrementSessionID();
            }
            else
            {
                IsInputTextEnabled = true;
            }
        }

        public void UpdateMessageQueueStatus(int receivedMessages, int totalMessagesInQueue)
        {
            if (totalMessagesInQueue > 10)
            {
                this.RemainingMessages = totalMessagesInQueue - receivedMessages;
            }
        }
        public override void Cleanup()
        {
            if (this.LastMessageReceivedTimer != null)
            {
                this.LastMessageReceivedTimer.Stop();
                this.LastMessageReceivedTimer = null;
            }
            this.ChatLogTimer.Dispose();
            this.ClientLines = null;
            this.Document = null;
            this.RemainingMessages = 0;
            this.IsBlinking = false;
            this.IsCurrentActiveTab = false;
            //this.MessageInputElementGenerators = null;
            //this.ConversationElementGenerators = null;
            //this.ConversationLineTransformers = null;

            base.Cleanup();
        }
        public bool Equals(ViewModelTab other)
        {
            return this.ID == other.ID;
        }
        public override bool Equals(object obj)
        {
            if (obj is ViewModelTab)
                return Equals((ViewModelTab)obj);
            else
                return base.Equals(obj);
        }
        public override int GetHashCode()
        {
            return this.ID.GetHashCode();
        }
    }
}
